Paperless-NGX in Docker installieren

Ich nutze Paperless inzwischen schon gut zwei Jahre – und ich finde es einfach klasse!

Ich habe keine Papiere mehr rumfliegen, scanne alles mit meinem Laserdrucker ein und lasse mir die Dokumente direkt per E-Mail an mein Paperless-Postfach schicken. Das hat mir im Alltag so vieles einfacher gemacht.

Und genau deshalb möchte ich heute zeigen, wie du Paperless-NGX ganz einfach mit Docker installieren kannst.

🚀 Warum Docker?

Ich sehe immer wieder, dass viele versuchen, Paperless direkt in einem LXC-Container oder sogar bare-metal auf ihrem System zu installieren.
Es gibt auch einige Skripte, die das automatisieren – das kann funktionieren, ist aber oft weniger flexibel.

Ich selbst setze lieber auf Docker, weil es für mich einfach die sauberste und wartungsfreundlichste Lösung ist.
Mit Docker kann ich Paperless klar getrennt von meinem System betreiben, Backups unkompliziert anlegen, Updates sicher einspielen und das Setup bei Bedarf schnell auf einen anderen Host umziehen.

Darum möchte ich dir in diesem Beitrag zeigen, wie du Paperless-NGX mit Docker installierst – stabil, nachvollziehbar und ohne Frickelei.

🧩 Voraussetzungen

Bevor es losgeht, brauchst du natürlich eine funktionierende Docker-Umgebung.
Wenn du Docker noch nicht installiert hast, findest du hier eine Schritt-für-Schritt-Anleitung, wie du das auf Ubuntu mit einem Script erledigst:

Sobald Docker läuft, kann’s direkt weitergehen.

🛠️ Vorbereitung

Damit Paperless-NGX gleich verwendet werden kann, richten wir erst einmal die nötigen Ordner auf dem System ein.
Das sorgt dafür, dass deine Daten sauber getrennt sind – also z. B. Dokumente, Medien oder temporäre Dateien.

Öffne dazu dein Terminal und führe die folgenden Befehle Schritt für Schritt aus:

👉 1. Einen neuen Projektordner erstellen

Hier legen wir das Hauptverzeichnis für Paperless an:

mkdir paperless-ngx

👉 2. In das Verzeichnis wechseln

Damit du direkt im richtigen Ordner arbeitest, wechselst du anschließend hinein:

cd paperless-ngx

👉 3. Unterordner für Daten und Medien anlegen

Paperless benötigt verschiedene Verzeichnisse für den Betrieb – z. B. zum Speichern, Verarbeiten und Exportieren von Dokumenten.
Diese erstellen wir mit einem einzigen Befehl:

mkdir -p ./data/paperless-trash ./data/paperless-consume ./data/paperless-export ./data/paperless-media ./data/paperless-data

👉 4. Zugriffsrechte anpassen

Damit Paperless später auf die Verzeichnisse zugreifen kann, setzen wir die richtigen Berechtigungen.
In der Regel läuft Paperless unter dem Benutzer mit der UID 1000 (siehe user: "1000:1000" in Docker-Compose), das ist meist dein Standardbenutzer:

chown -R 1000:1000 ./data/paperless-trash ./data/paperless-consume ./data/paperless-export ./data/paperless-media ./data/paperless-data

Damit ist die grundlegende Struktur vorbereitet.
Du hast jetzt ein sauberes Arbeitsverzeichnis, in dem später alle Container ihre Daten ablegen.

🌐 Docker-Netzwerk erstellen

Damit Paperless und seine Dienste sich untereinander erreichen können, legen wir ein eigenes Docker-Netzwerk an:

docker network create --subnet=172.16.9.0/24 paperless.dockernetwork.local

So bleiben alle Container im gleichen Subnetz und du hast eine saubere Trennung von anderen Projekten.

🔑 Secret Key und Passwörter erstellen

Paperless braucht einen sogenannten Secret Key, um sensible Daten sicher zu verschlüsseln.
Den erzeugst du einmalig so:

tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64; echo

💡 Den Key solltest du unbedingt sicher aufbewahren.
Er wird später in der Variablen PAPERLESS_SECRET_KEY eingetragen.

Anschließend brauchen wir noch zwei Passwörter:
Eines für die Datenbank (POSTGRES_PASSWORD) und eines für den Paperless-Admin (PAPERLESS_ADMIN_PASSWORD).
Führe den folgenden Befehl zweimal aus und notiere beide Kennwörter:

tr -dc 'A-Za-z0-9' </dev/urandom | head -c 10; echo

Diese Passwörter setzen wir ebenfalls gleich in der docker-compose.yaml ein.

⚙️ Docker-Compose erstellen

Wenn die Vorbereitung abgeschlossen ist, legst du die Compose-Datei an.
Darin definieren wir alle Container, die Paperless braucht – also den Webserver, die Datenbank, Redis und die unterstützenden Dienste wie Tika und Gotenberg.

Erstelle die Datei mit:

nano docker-compose.yaml

Hier ein vollständiges Beispiel (ich habe für diese Beitrag neue Kennwörter und Keys generiert):

---
services:

  paperless-webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    restart: unless-stopped
    container_name: paperless-webserver
    user: "1000:1000"
    depends_on:
      - paperless-db
      - paperless-redis
      - paperless-tika
      - paperless-gotenberg
    networks:
      paperless.dockernetwork.local:
        ipv4_address: 172.16.9.10
    ports:
      - "8080:8000"
    volumes:
      - ./data/paperless-data:/usr/src/paperless/data
      - ./data/paperless-media:/usr/src/paperless/media
      - ./data/paperless-export:/usr/src/paperless/export
      - ./data/paperless-consume:/usr/src/paperless/consume
      - ./data/paperless-trash:/usr/src/paperless/trash
    environment:
      PAPERLESS_TRASH_DIR: ../trash
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS: "GlGRD3ZwRn"
      PAPERLESS_FILENAME_FORMAT: "{owner_username}/{document_type}/{correspondent}/{created_year}/{title}"
      PAPERLESS_REDIS: redis://paperless-redis:6379
      PAPERLESS_DBHOST: paperless-db
      PAPERLESS_TIKA_ENABLED: "true"
      PAPERLESS_TIKA_ENDPOINT: http://paperless-tika:9998
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://paperless-gotenberg:3000
      PAPERLESS_FILENAME_FORMAT_REMOVE_NONE: "true"
      PAPERLESS_LOGROTATE_MAX_SIZE: 1
      PAPERLESS_SECRET_KEY: "B97cnMktjsRsUrJsppwKyJPETJQCLQHPni6OJDEeXwbLM2TT4li4zhMCiuY1JthD"
      PAPERLESS_URL: "http://paperless.cleveradmin.local:8080"
      PAPERLESS_ALLOWED_HOSTS: "paperless.cleveradmin.local"
      PAPERLESS_CORS_ALLOWED_HOSTS: "http://paperless.cleveradmin.local"
      PAPERLESS_ADMIN_USER: "paperless-admin"
      PAPERLESS_ADMIN_MAIL: papaerless-admin@cleveradmin.local
      PAPERLESS_ADMIN_PASSWORD: "JCxl3VkGBc"
      PAPERLESS_OCR_LANGUAGE: deu+eng
      PAPERLESS_OCR_MODE: skip
      PAPERLESS_OCR_SKIP_ARCHIVE_FILE: never
      PAPERLESS_OCR_DESKEW: true
      PAPERLESS_OCR_ROTATE_PAGES: true
      PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD: 12
      PAPERLESS_OCR_OUTPUT_TYPE: pdfa
      PAPERLESS_TASK_WORKERS: 3
      PAPERLESS_OCR_USER_ARGS: '{"invalidate_digital_signatures": true}'
      PAPERLESS_WORKER_TIMEOUT: 3600
      PAPERLESS_TIME_ZONE: Europe/Berlin
      PAPERLESS_CONSUMER_RECURSIVE: true
      PAPERLESS_CONSUMER_BARCODE_SCANNER: "PYZBAR"
      PAPERLESS_FILENAME_DATE_ORDER: "YMD"
      PAPERLESS_CONSUMER_POLLING: 0
      PAPERLESS_CONSUMER_POLLING_RETRY_COUNT: 5
      PAPERLESS_AUDIT_LOG_ENABLED: true
      PAPERLESS_WEBSERVER_WORKERS: 1
      USERMAP_GID: 1000
      USERMAP_UID: 1000
      PAPERLESS_APP_TITLE: "CleverAdmin Paperless-NGX"

  paperless-redis:
    image: docker.io/library/redis:7
    container_name: paperless-redis
    restart: always
    volumes:
      - ./data/redis-data:/data
    networks:
      paperless.dockernetwork.local:
        ipv4_address: 172.16.9.11

  paperless-db:
    image: docker.io/library/postgres:17
    restart: always
    container_name: paperless-db
    volumes:
      - ./data/pg-data:/var/lib/postgresql/data
    networks:
      paperless.dockernetwork.local:
        ipv4_address: 172.16.9.12
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: "GlGRD3ZwRn"

  paperless-tika:
    image: ghcr.io/paperless-ngx/tika:latest
    container_name: paperless-tika
    restart: unless-stopped
    networks:
      paperless.dockernetwork.local:
        ipv4_address: 172.16.9.13
    ports:
      - "9998:9998"

  paperless-gotenberg:
    image: docker.io/gotenberg/gotenberg:latest
    container_name: paperless-gotenberg
    restart: unless-stopped
    networks:
      paperless.dockernetwork.local:
        ipv4_address: 172.16.9.14
    ports:
      - "3000:3000"
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"

networks:
  paperless.dockernetwork.local:
    external: true

In meinem Beispiel verwende ich zu Demonstrationszwecken die Domain cleveradmin.local – diese musst du natürlich an deine eigene Umgebung anpassen.
Wenn du Paperless z. B. unter einer anderen internen Domain oder IP-Adresse betreibst, ersetze einfach alle Vorkommen von cleveradmin.local entsprechend.

Außerdem musst du an mehreren Stellen in der Compose-Datei deine Kennwörter und den Secret Key eintragen, die du vorher generiert hast.
Konkret betrifft das folgende Variablen:

  • POSTGRES_PASSWORD (unter paperless-db)
  • PAPERLESS_DBPASS (unter paperless-webserver)
  • PAPERLESS_ADMIN_PASSWORD
  • PAPERLESS_SECRET_KEY

⚙️ Erklärung einiger Paperless-Variablen

Paperless bringt viele Konfigurationsmöglichkeiten mit. Einige davon sind Pflicht, andere richtig praktisch, wenn man die Funktionen kennt.
Hier findest du eine kleine Übersicht über nützliche Variablen, die du dir einmal genauer anschauen solltest:

  • PAPERLESS_FILENAME_FORMAT
    → Steuert, wie Paperless deine Dokumente intern benennt. Du kannst Platzhalter wie {created_year}, {title}, {correspondent} oder {document_type} verwenden, um deine Ablagestruktur automatisch zu gestalten. Das kannst du allerdings auch gerne später nachträglich im Webinterface konfigurieren. Ich hab das auch so gemacht.
  • PAPERLESS_FILENAME_DATE_ORDER
    → Gibt an, in welcher Reihenfolge das Datum im Dateinamen erscheint. Zum Beispiel YMD für Jahr-Monat-Tag.
  • PAPERLESS_CONSUMER_POLLING
    → Definiert, wie oft Paperless das „Consume“-Verzeichnis nach neuen Dateien durchsucht (in Sekunden).
    Wenn du hier 0 einträgst, scannt Paperless kontinuierlich.
  • PAPERLESS_CONSUMER_RECURSIVE
    → Wenn auf true, durchsucht Paperless auch Unterordner im Consume-Verzeichnis – sehr praktisch, wenn du deine Scans nach Themen sortieren möchtest.
  • PAPERLESS_CONSUMER_BARCODE_SCANNER
    → Aktiviert die automatische Barcode-Erkennung, z. B. mit PYZBAR.
    Damit kannst du Dokumente über Barcodes direkt bestimmten Kategorien zuordnen.
  • PAPERLESS_OCR_LANGUAGE
    → Gibt die Sprache(n) für die Texterkennung an.
    deu+eng ist z. B. eine gute Kombination für deutsche und englische Dokumente.
  • PAPERLESS_OCR_MODE
    → Legt fest, ob OCR (Texterkennung) immer durchgeführt wird, nur bei Bedarf, oder übersprungen wird.
    Mit skip sparst du Rechenzeit, wenn deine PDFs bereits durchsuchbar sind.
  • PAPERLESS_OCR_OUTPUT_TYPE
    → Bestimmt das Ausgabeformat. pdfa sorgt dafür, dass archivierte PDF-Dateien mit Textlayer erzeugt werden.
  • PAPERLESS_AUDIT_LOG_ENABLED
    → Aktiviert das interne Audit-Log. Damit kannst du später nachvollziehen, wer was wann gemacht hat.
  • PAPERLESS_WORKER_TIMEOUT
    → Legt fest, wie lange Paperless auf die Verarbeitung einzelner Aufgaben wartet.
    Das ist hilfreich, wenn du sehr große Dokumente hast.
  • PAPERLESS_APP_TITLE
    → Der Name deiner Paperless-Instanz – wird in der Weboberfläche angezeigt.
    Ideal, wenn du mehrere Instanzen betreibst oder dein Setup personalisieren willst.

Wenn du noch mehr über die unzähligen Paperless-Variablen erfahren möchtest, lohnt sich ein Blick in die offizielle Dokumentation:

▶️ Paperless starten

Nun kannst du Paperless direkt starten:

docker-compose up

Ich mache das gerne im Vordergrund, um direkt die Logs zu sehen und zu prüfen, ob alles sauber läuft.
Wenn alles funktioniert, beendest du den Startvorgang mit STRG + C und startest die Container dann im Hintergrund:

docker-compose up -d

Mit ein paar Sekunden Geduld sollte Paperless nun unter http://deine-server-ip:8080 erreichbar sein.
Melde dich mit dem Benutzer und Passwort an, die du in der Compose-Datei angegeben hast.

👤 Paperless-Admin absichern

Wenn der erste Login funktioniert, kannst du diese drei Einträge aus deiner docker-compose.yaml löschen:

PAPERLESS_ADMIN_USER: "paperless-admin"
PAPERLESS_ADMIN_MAIL: papaerless-admin@cleveradmin.local
PAPERLESS_ADMIN_PASSWORD: "JCxl3VkGBc"

Die Daten sind nun in der Datenbank gespeichert und müssen nicht mehr im Klartext in der Compose-Datei stehen.

Weiterhin empfehle ich, einen separaten Benutzer für die tägliche Arbeit zu erstellen und den Admin-Account nur für administrative Aufgaben zu verwenden.
So bleibt dein Setup sicher und übersichtlich.

🧾 Paperless im Alltag

Damit hast du jetzt ein voll funktionsfähiges Paperless-System für den internen Gebrauch.
Du kannst Dokumente hochladen, organisieren und später gezielt durchsuchen.

Wenn du magst, kannst du dir auch überlegen, wie du Dokumententypen, Speicherpfade oder Korrespondenten strukturierst – das hilft dir langfristig, alles sauber zu halten. Ich hab das genau so gemacht.

Ich nutze Paperless inzwischen wirklich täglich.

Es ist ein solides, ausgereiftes Tool, das mir den Umgang mit Rechnungen, Verträgen und privaten Unterlagen enorm erleichtert.
Sogar meine Freundin habe ich inzwischen überzeugt – wir nutzen es jetzt gemeinsam. 😊

💡 Nächste Schritte

Folgende Themen habe ich hier bewusst noch nicht umgesetzt, weil das den Beitrag sprengen würde:

  • Paperless hinter einem Reverse Proxy bereit stellen
  • E-Mail-Versand und Empfang in Paperless
  • Anlage von Dokumententypen, Speicherpfaden und Korrespondenten
  • Exportfunktionen mit Paperless

Wenn dich eines dieser Themen interessiert, schreib es gerne unten in die Kommentare!
Ich freue mich auf dein Feedback und du kannst dich bald auf einen neuen Beitrag zu Paperless-NGX freuen! 😎

👥 Techniverse Community

Lust auf Austausch rund um Matrix, Selfhosting und andere smarte IT-Lösungen?
In der Techniverse Community triffst du Gleichgesinnte, kannst Fragen stellen oder einfach nerdigen Talk genießen. 🚀

👉 Jetzt der Gruppe auf Matrix beitreten
~ Direkte Raumadresse: #community:techniverse.net

👉 Für lockere Gespräche abseits der Kernthemen komm in den Talkraum
~ Direkte Raumadresse: #talk:techniverse.net

Wir freuen uns, wenn du dabei bist!

Vielen Dank fürs Teilen!